/*
 * SPDX-FileCopyrightText: (C) 2013-2022 Daniel Nicoletti <dantti12@gmail.com>
 * SPDX-License-Identifier: BSD-3-Clause
 */
#ifndef CUTELEE_VIEW_H
#define CUTELEE_VIEW_H

#include <Cutelyst/Plugins/View/cutelee_export.h>
#include <Cutelyst/View>

#include <QLocale>
#include <QObject>
#include <QStringList>
#include <QVector>

class QTranslator;

namespace Cutelee {
class Engine;
}

namespace Cutelyst {

class CuteleeViewPrivate;
/**
 * \ingroup plugins-view
 * \headerfile "" <Cutelyst/Plugins/View/Cutelee/cuteleeview.h>
 * \brief A view that renders templates using Cutelee engine.
 *
 * The %CuteleeView is a view handler that renders templates using the
 * <a href="https://github.com/cutelyst/cutelee">Cutelee</a> engine. It loads
 * template files from the included paths and populates them with stash values.
 *
 * The view should be initialized in you reimplementation of Application::init(),
 * it can than be easily called from a RenderView action. You can specify a name
 * for your view that can be either set dynamically via Context::setCustomView()
 * or via the <tt>:%View("name")</tt> argument of the RenderView action. A view with an
 * empty name will be the default view.
 *
 * The view will try to find the template file name to render in the stash using the
 * \c "template" key. If that key is not available, it will try to find a template
 * named after the private name of the current action.
 *
 * <h3>Included additional tags</h3>
 * Beside the tags included in Cutelee, %Cutelyst provides some own tags:
 *
 * \par c_uri_for
 * \parblock
 * This tag uses Context::uriFor() to create an URL for a specific path populated
 * with arguments and optional query parameters. Path is the only required argument
 * for the tag, the QUERY keyword must preceed query parameters.
 *
 * <tt>{% c_uri_for "/path" "arg1" "arg2" QUERY "foo=bar" c.req.queryParams %}</tt>
 * \endparblock
 *
 * \par c_csrf_token
 * \parblock
 * This tag creates a hidden HTML form input field containing a CSRF protection token
 * generated by CSRFProtection::getTokenFormField(). It has no parameters and should be
 * used inside form tags.
 *
 * <tt>{% c_csrf_token %}</tt>
 * \endparblock
 *
 * \par c_csrf_token_value
 * \parblock
 * This tag writes the current CSRF token as returned by CSRFProtection::getToken(). This can
 * be useful if you only need the token value instead of a complete form field to use it for
 * example in a meta tag for access with JavaScript.
 *
 * <tt>{% c_csrf_token_value %}</tt>
 * \endparblock
 *
 * <h3>Usage example</h3>
 * Initialize the view in your reimplementation of Application::init():
 * @code{.cpp}
 * #include <Cutelyst/Plugins/View/Cutelee/cuteleeview.h>
 *
 * using namespace Cutelyst;
 *
 * bool MyApp::init()
 * {
 *      // other initialization stuff
 *      // ...
 *
 *      // register the view to the application without a name
 *      // as our default view
 *      auto view = new CuteleeView(this);
 *      view->setCached(true);
 *      view->setIncludePaths({"/path/to/my/tempalte/files"});
 *      // if you want to use Cutelee tags for internationalization
 *      // and localization, you have to explicitely load that Cutelee plugin library
 *      view->engine()->addDefaultLibrary("cutelee_18ntags");
 * }
 * @endcode
 *
 * Load the view with a RenderView action on for example the End() method of your root controller:
 * @code{.h}
 * #include <Cutelyste/Controller>
 *
 * using namespace Cutelyst;
 *
 * class Root : public Controller
 * {
 *      Q_OBJECT
 * public:
 *      explicit Root(QObject *parent = nullptr);
 *      ~Root() override = default;
 *
 *      C_ATTR(index, :Path :Args(0))
 *      void index(Context *c);
 *
 * private:
 *      // Here we specify the RenderView action using the default nameless view
 *      // created in our app’s init method. We could use the additional :View("foo")
 *      // argument to select a specific named view if we would have more than one.
 *      C_ATTR(End, :ActionClass("RenderView"))
 *      void End(Context *c) { Q_UNUSED(c); }
 * }
 * @endcode
 *
 * Inside the implementation of the index method from above we now set the name of
 * the template file to use:
 * @code{.cpp}
 * void Root::index(Context *c)
 * {
 *      // other stuff for our index method
 *      // ...
 *
 *      // the CuteleeView called by the RenderView action of the End method will now try
 *      // to load the template file from /path/to/my/template/files/frontend/index.html
 *      c->setStash("template", "frontend/index.html");
 * }
 * @endcode
 *
 * @logcat{view.cutelee}
 */
class CUTELYST_VIEW_CUTELEE_EXPORT CuteleeView final : public View
{
    Q_OBJECT
    Q_DECLARE_PRIVATE(CuteleeView)
public:
    /**
     * Constructs a %CuteleeView object with the given \a parent and \a name.
     *
     * The \a name can be used to specify different views that can be called either dynamically
     * by Context::setCustomView() or with the <tt>:%View("name")</tt> argument of the RenderView
     * action.
     */
    explicit CuteleeView(QObject *parent = nullptr, const QString &name = {});

    Q_PROPERTY(QStringList includePaths READ includePaths WRITE setIncludePaths NOTIFY changed)
    /**
     * Returns the list of include paths.
     * \sa setIncludePaths()
     */
    QStringList includePaths() const;

    /**
     * Sets the list of include paths which will be looked for when resolving templates files.
     * \sa includePaths()
     */
    void setIncludePaths(const QStringList &paths);

    Q_PROPERTY(
        QString templateExtension READ templateExtension WRITE setTemplateExtension NOTIFY changed)
    /**
     * Returns the template extension, defaults to ".html".
     * \sa setTemplateExtenion()
     */
    QString templateExtension() const;

    /**
     * Sets the template extension, defaults to ".html".
     * \sa templateExtension()
     */
    void setTemplateExtension(const QString &extension);

    Q_PROPERTY(QString wrapper READ wrapper WRITE setWrapper NOTIFY changed)

    /**
     * Returns the template wrapper.
     * \sa setWrapper()
     */
    [[nodiscard]] QString wrapper() const;

    /**
     * Sets the template wrapper \a name, the template will be rendered into
     * content variable in which the wrapper template should render.
     * \sa wrapper()
     */
    void setWrapper(const QString &name);

    Q_PROPERTY(bool cache READ isCaching WRITE setCache NOTIFY changed)
    /**
     * Returns \c true if caching is enabled.
     * \sa setCache()
     */
    bool isCaching() const;

    /**
     * Sets if template caching should be done, this increases
     * performance at the cost of higher memory usage.
     * \sa isCaching()
     */
    void setCache(bool enable);

    /**
     * Returns the Cutelee::Engine pointer that is used by this engine.
     */
    [[nodiscard]] Cutelee::Engine *engine() const;

    /**
     * When called, setCache() is set to \c true and templates are loaded.
     */
    void preloadTemplates();

    QByteArray render(Context *c) const override final;

    /**
     * Adds a \a translator for the specified \a locale to the list of translators.
     *
     * \par Example usage
     * \code{.cpp}
     * bool MyCutelystApp::init()
     * {
     *      // ...
     *
     *      auto view = new CuteleeView(this);
     *
     *      auto deDeTrans = new QTranslator(this);
     *      if (deDeTrans->load(QStringLiteral("de_DE"),
     *                          QStringLiteral("/path/to/my/translations")) {
     *           view->addTranslator(QLocale("de_DE"), deDeTrans);
     *      }
     *
     *      auto ptBrTrans = new QTranslator(this);
     *      if (ptBrTrans->load(QStringLiteral("pt_BR"),
     *                          QStringLiteral("/path/to/my/translations")) {
     *           view->addTranslator(QLocale("pt_BR"), ptBrTrans);
     *      }
     *
     *      // ...
     * }
     * \endcode
     *
     * It is mostly easier to use loadTranslationsFromDir() to load all available translations
     * in a bunch.
     *
     * \sa \ref translations
     *
     * \since Cutelyst 1.5.0
     */
    void addTranslator(const QLocale &locale, QTranslator *translator);

    /**
     * Adds a \a translator for the specified \a locale to the list of translators.
     *
     * The \a locale string should be parseable by QLocale.
     *
     * \overload
     *
     * \sa loadTranslationsFromDir()
     * \sa \ref translations
     *
     * \since Cutelyst 1.4.0
     */
    void addTranslator(const QString &locale, QTranslator *translator);

    /**
     * Dynamically adds translation \a catalog at \a path to the translator.
     *
     * Translation catalogs can be used to dynamically integrate translations into the
     * CuteleeView, for example for plugins and themes. The \a catalog could be the name
     * of an extension for example that is loaded from a locale specifc directory under \a path.
     *
     * The catalog will be loaded in the following way: /path/locale/catalog, for example
     * \c /usr/share/mycutelystapp/l10n/de_DE/fancytheme.qm. The current locale is defined by
     * Context::locale() when rendering the theme. The \a path \c /usr/share/myapp/l10n would
     * then contain locale specific subdirectories like de_DE, pt_BR, etc. that contain the
     * translation files named by \a catalog.
     *
     * \par Usage example:
     * \code{.cpp}
     * bool MyCutelystApp::init()
     * {
     *      // ...
     *
     *      auto view = new CuteleeView(this);
     *      view->addTranslationCatalog(QStringLiteral("/usr/share/mycutelystapp/l10n"),
     *                                  QStringLiteral("fancytheme"));
     *
     *      // ...
     * }
     * \endcode
     *
     * \sa \ref translations
     *
     * \since Cutelyst 1.5.0
     */
    void addTranslationCatalog(const QString &path, const QString &catalog);

    /**
     * Adds a dictionary of translation catalogs and paths to the translator.
     *
     * The \a key of the QHash is the name of the catalog, the \a value is the path.
     * See addTranslationCatalog() for more information about translation catalogs.
     *
     * \sa \ref translations
     *
     * \since Cutelyst 1.5.0
     */
    void addTranslationCatalogs(const QMultiHash<QString, QString> &catalogs);

    /**
     * Loads translations for a specific @p filename from a single directory and returns a list of
     * added locales.
     *
     * This can be used to load translations for a template if the translation file names follow a
     * common schema. Let us assume you organised your translation files as follows:
     * @li @c /usr/share/myapp/translations/mytemplate_de.qm
     * @li @c /usr/share/myapp/translations/mytemplate_pt_BR.qm
     * @li @c ...
     *
     * You can then use loadTranslationsFromDir() on your registered CuteleeView object as follows:
     * @code{.cpp}
     * bool MyApp::init()
     * {
     *      auto view = new CuteleeView(this);
     *      view->loadTranslationsFromDir(QStringLiteral("mytemplate"),
     *                                    QStringLiteral("/usr/share/myapp/translations"),
     *                                    QStringLiteral("_"));
     * }
     * @endcode
     *
     * @p prefix is the part between the file name and the locale part. In the example above it is
     * @c "_", if it is not set the default @c "." will be used. The
     * @p suffix is the file name suffix that defaults to <code>".qm"</code>.
     *
     * @sa addTranslator(), loadTranslationsFromDir()
     * @sa @ref translations
     *
     * @since Cuteylst 2.1.0
     */
    QVector<QLocale> loadTranslationsFromDir(const QString &filename,
                                             const QString &directory,
                                             const QString &prefix = QStringLiteral("."),
                                             const QString &suffix = QStringLiteral(".qm"));

Q_SIGNALS:
    void changed();
};

} // namespace Cutelyst

#endif // CUTELEE_VIEW_H
